"""NavalaCurve and its descendants."""
import PyQt4.Qt as Qt
import numpy as np
import time
import pyqtgraph as gr
from pyqtgraph import QtCore, QtGui
import logging

from misc import misc


class CurveFramework(object):
    """Use together with a gr.GraphicsObject to create a curve class
    
    One important thing is that setting data does not cause an urge to repaint.
    
    TODO: I could create a NavalCurveFramework class with all this and with this allow to inherit from the existing pyqtgraph classes, e.g. Infinite line for some oscillator threshold.
    TODO: With this, I could always inherit from a gr.GraphicsObject descendant AND NavalaCurveFramework
    TODO: The advantage is that I can have everything that 
        
    
    """
    
    
# Check this out, might be useful:
####    def mouseClickEvent(self, ev):
####        if not self.clickable or ev.button() != QtCore.Qt.LeftButton:
####            return
####        ev.accept()
####        self.sigClicked.emit(self)
    
    
#    td = None
    def __init__(self, drawer, *args, **kwargs):
        """
        
        Keyword arguments:
            name (Optional) -- Optional, but there is no default name. If
                passed, the Drawer will use it as key to its curves[]
                property; otherwise the Drawer will make up an integer index
        """
        self._drawer = drawer
        # Internal data. This is an arbitrary object, not necessarily TimeData
        self._data = None
        self.name = kwargs.get("name", None)
        drawer._AddCurve(self) # This will unfortunately try to paint
        # TimeData
        
    def SetData(self, data):
        """Sets internal _data. Note that data is not necessarily TimeData."""
        self._data = data
    
    def HasData(self):
        return not misc.isempty(self._data)

    def Update(self):
        """Bug check plus calls _Update().
        Not checking for data anymore."""
        #if self._data is None:
        #    raise RuntimeError("Update() being called before setting data (this is a bug)!")
        self._Update()

    def _Update(self):
        """Method to force repaint.
        
        It is _Update()'s task to transfer data to the gr.GraphicObject fields
        or to force repaint somehow.
        
        Default is to call informViewBoundsChanged()."""
        self.informViewBoundsChanged()

        
        
# TODO: I think this is not being called!!!

        
###        loggin.debug("NavalaCurve.Update() being called, actually!!!!!!!!!!!!!!!!")
###        loggin.debug("NavalaCurve.Update() being called, actually!!!!!!!!!!!!!!!!")
###        loggin.debug("NavalaCurve.Update() being called, actually!!!!!!!!!!!!!!!!")
###        loggin.debug("NavalaCurve.Update() being called, actually!!!!!!!!!!!!!!!!")
###        loggin.debug("NavalaCurve.Update() being called, actually!!!!!!!!!!!!!!!!")
###        self.informViewBoundsChanged() # Note what is assumed


class Path(gr.PlotDataItem, CurveFramework):
    """Draws path using fieldName from a TimeData."""
###    # Defaults to white pen of width 1, DASHED (to test)
###    @property
###    def pen(self):
###        return self._pen

    @property
    def fieldName(self):
        return self._fieldName
    @fieldName.setter
    def fieldName(self, x):
        self._fieldName = x
    
    def __init__(self, drawer, *args, **kwargs):
        """
        Keyword arguments:
            fieldName="y"
        """
        gr.PlotDataItem.__init__(self, *args, **kwargs)
        CurveFramework.__init__(self, drawer, *args, **kwargs)
        
        # FieldName will be automatically set by the Drawer that is controlling
        # the curve, e.g., see PathDrawer._Use(). So, better not to have a
        # default.
        self._fieldName = None

###        self._pen = gr.mkPen(color=(0, 192, 192), width=1, style=QtCore.Qt.SolidLine)

###    def paint(self, painter, *args):
###        if not self.HasData():
###            return
###        td = self._data
###        x = td.timestamp2
###        y = self._data.__getattribute__(self.fieldName)
###        path = gr.arrayToQPath(x, y)
###        painter.setPen(self._pen)
###        painter.drawPath(path)
    
###    def boundingRect(self):
###        """Returns the bounding rectangle of the data.
###        
###        For the y boundaries, uses low/high if they are used, or open/close
###        otherwise.
###        """
###        if not self.HasData():
###            return Qt.QRectF()
###        td = self._data
###        x = td.timestamp2
###        y = self._data.__getattribute__(self.fieldName)
###        xmin, xmax, ymin, ymax = np.min(x), np.max(x), np.min(y), np.max(y)
###        return Qt.QRectF(xmin, ymin, xmax-xmin, ymax-ymin)


    def _Update(self):
        if self._fieldName is None:
            raise RuntimeError("PathCurve fieldName has not been set (this is a bug)!")

        self.setData(x=self._data.timestamp2, y=self._data.__getattribute__(self.fieldName))
        


class HLine(gr.InfiniteLine, CurveFramework):
    def __init__(self, drawer, *args, **kwargs):
        gr.InfiniteLine.__init__(self, angle=0, movable=False, *args, **kwargs)
        CurveFramework.__init__(self, drawer, *args, **kwargs)
        
        # FieldName will be automatically set by the Drawer that is controlling
        # the curve, e.g., see PathDrawer._Use(). So, better not to have a
        # default.
        self._fieldName = None



################################################class NavalaCurve(gr.GraphicsObject):
################################################    """Graphics object with an associated TimeData.
################################################    
################################################    One important thing is that setting data does not cause an urge to repaint.
################################################    
################################################    TODO: I could create a NavalCurveFramework class with all this and with this allow to inherit from the existing pyqtgraph classes, e.g. Infinite line for some oscillator threshold.
################################################    TODO: With this, I could always inherit from a gr.GraphicsObject descendant AND NavalaCurveFramework
################################################    TODO: The advantage is that I can have everything that 
################################################        
################################################    
################################################    """
################################################    
################################################    
################################################# Check this out, might be useful:
####################################################    def mouseClickEvent(self, ev):
####################################################        if not self.clickable or ev.button() != QtCore.Qt.LeftButton:
####################################################            return
####################################################        ev.accept()
####################################################        self.sigClicked.emit(self)
################################################    
################################################    
#################################################    td = None
################################################    def __init__(self, drawer, *args, **kwargs):
################################################        """
################################################        
################################################        Keyword arguments:
################################################            name (Optional) -- Optional, but there is no default name. If
################################################                passed, the Drawer will use it as key to its curves[]
################################################                property; otherwise the Drawer will make up an integer index
################################################        """
################################################        gr.GraphicsObject.__init__(self, *args, **kwargs)
################################################        self._drawer = drawer
################################################        self._data = None
################################################        self.name = kwargs.get("name", None)
################################################        drawer._AddCurve(self) # This will unfortunately try to paint
################################################        # TimeData
################################################        
################################################    def SetData(self, td):
################################################        """Sets internal _data.
################################################        
################################################        Does not update because this will be done from a different path."""
################################################        self._data = td
################################################    
################################################    def HasData(self):
################################################        return not misc.isempty(self._data)
################################################
################################################    def Update(self):
################################################        loggin.debug("NavalaCurve.Update() being called, actually!!!!!!!!!!!!!!!!")
################################################        loggin.debug("NavalaCurve.Update() being called, actually!!!!!!!!!!!!!!!!")
################################################        loggin.debug("NavalaCurve.Update() being called, actually!!!!!!!!!!!!!!!!")
################################################        loggin.debug("NavalaCurve.Update() being called, actually!!!!!!!!!!!!!!!!")
################################################        loggin.debug("NavalaCurve.Update() being called, actually!!!!!!!!!!!!!!!!")
################################################        self.informViewBoundsChanged()
################################################
######################
######################class Path(NavalaCurve):
######################    """Draws path using fieldName from a TimeData."""
######################    # Defaults to white pen of width 1, DASHED (to test)
######################    @property
######################    def pen(self):
######################        return self._pen
######################    
######################    def __init__(self, *args, **kwargs):
######################        NavalaCurve.__init__(self, *args, **kwargs)
######################        self._pen = gr.mkPen(color=(0, 192, 192), width=1, style=QtCore.Qt.SolidLine)
######################
######################    def paint(self, painter, *args):
######################        if not self.HasData():
######################            return
######################        td = self._data
######################        x = td.timestamp2
######################        y = self._data.__getattribute__(self.fieldName)
######################        path = gr.arrayToQPath(x, y)
######################        painter.setPen(self._pen)
######################        painter.drawPath(path)
######################    
######################    def boundingRect(self):
######################        """Returns the bounding rectangle of the data.
######################        
######################        For the y boundaries, uses low/high if they are used, or open/close
######################        otherwise.
######################        """
######################        if not self.HasData():
######################            return Qt.QRectF()
######################        td = self._data
######################        x = td.timestamp2
######################        y = self._data.__getattribute__(self.fieldName)
######################        xmin, xmax, ymin, ymax = np.min(x), np.max(x), np.min(y), np.max(y)
######################        return Qt.QRectF(xmin, ymin, xmax-xmin, ymax-ymin)
######################


## Create a subclass of GraphicsObject.
## The only required methods are paint() and boundingRect() 
## (see QGraphicsItem documentation)
class CandlestickCurve(gr.GraphicsObject, CurveFramework):
    def __init__(self, drawer, *args, **kwargs):
        """Candlesticks using open, close, high and low price information.
        
        high and low are optional; if None, only the candle bodies will be
        drawn.

        open_ and close must be sequences with a shape (N,), and
        high and low must be either None or also sequences with a shape (N,).
        
        candlePen is the pen used to draw the candle shadows and body outlines
        
        upColor is the color used to paint the candle bodies when close > open_

        downColor is the color used to paint the candle bodies when 
        open_ > close
        """
        gr.GraphicsObject.__init__(self, *args, **kwargs)
        CurveFramework.__init__(self, drawer, *args, **kwargs)
        self.upColor = Qt.QColor(0, 192, 0)
        self.downColor = Qt.QColor(255, 0, 0)
        
        self.counter = 0 # debug thing

    def DrawCandles(self, painter):
        # These 3 lines are for debugging/optimizing
        #t = time.time()
#        print "COUNTER: %04d DRAWING %d CANDLES" % (self.counter, len(self._data))
#        self.counter += 1

        td = self._data
        x = td.timestamp2
        open_ = td.open
        close = td.close
        high = td.high
        low = td.low
        n, i, = len(x), 0
        flagBody = n <= 1000
        # Finds candle width (same unit as x). It must be a fraction of
        # the horizontal point spacing. But the latter may be varible, so
        # finds the minimum distance between x values to use as
        # "horizontal spacing"
        if len(td.timestamp) > 1:
            horizSpacing = np.min(np.diff(td.timestamp))            
        else:
            horizspacing = 60 # say, 60 seconds
        # candle horizontal span will be
        # x[i]-halfWidth until x[i]+halfWidth
        halfWidth = horizSpacing*0.375
        #print "FlagBody is %d; haldWidth=%f" % (flagBody, halfWidth)
        upPen = Qt.QPen(self.upColor)
        downPen = Qt.QPen(self.downColor)
        upBrush = Qt.QBrush(self.upColor, Qt.Qt.NoBrush)
        downBrush = Qt.QBrush(self.downColor, Qt.Qt.SolidPattern)
        #painter.setPen(self.candlePen)
                    
        for i in range(n):
            x1 = x[i]-halfWidth
            x2 = x[i]+halfWidth
            xi = x[i]

            # Body
            hei = abs(open_[i]-close[i])
            if close[i] >= open_[i]:
                painter.setBrush(upBrush)
                painter.setPen(upPen)
                bodyTop = close[i]
                bodyBottom = open_[i]
            else:
                painter.setBrush(downBrush)
                painter.setPen(downPen)
                bodyBottom = close[i]
                bodyTop = open_[i]
            
            if flagBody:
                painter.drawRect(QtCore.QRectF(
                     x1, open_[i], x2-x1, close[i]-open_[i]))

                # Shadows
                hii = high[i]
                loi = low[i]
                if high[i] > bodyTop:
                    painter.drawLine(QtCore.QLineF(xi, bodyTop, xi, hii))

                if low[i] < bodyBottom:
                    painter.drawLine(QtCore.QLineF(xi, loi, xi, bodyBottom))
            else:
                # Alternative drawing when the bodies are not distinguishable anyway
                painter.drawLine(QtCore.QLineF(xi, low[i], xi, high[i]))

        #print "Took %f seconds to draw candles" % (time.time()-t)
    
    def paint(self, p, *args):
        if self.HasData():
            self.DrawCandles(p)
    
    def boundingRect(self):
        """Returns the bounding rectangle of the data.
        
        For the y boundaries, uses low/high if they are used, or open/close
        otherwise.
        """
        if not self.HasData():
            return Qt.QRectF()
        td = self._data
        x = td.timestamp2
        xmin = np.min(x)
        xmax = np.max(x)
        if td.high is not None and len(td.high) > 0:
            ymin = np.min(td.low)
            ymax = np.max(td.high)
        else:
            ymin = np.min(np.min(td.open), np.min(td.close))
            ymax = np.max(np.max(td.open), np.max(td.close))

        try:
            return Qt.QRectF(xmin, ymin, xmax-xmin, ymax-ymin)
        except:
            pass


class VolumeCurve(gr.GraphicsObject, CurveFramework):
    """Curve that plots trade volume information."""

    def __init__(self, drawer, *args, **kwargs):
        gr.GraphicsObject.__init__(self, *args, **kwargs)
        CurveFramework.__init__(self, drawer, *args, **kwargs)
        self.setColor((25, 192, 0)) # Defaults to sth greenish
        
    def setColor(self, color):
        c = gr.mkColor(color)
        c.setAlpha(150)

        self.pen = Qt.QPen(c)
        self.brush = Qt.QBrush(c)

    def paint(self, painter, *args):
        #print "DRAWING VOLUME"
        if not self.HasData():
            return
        td = self._data
        x = td.timestamp2
        y = td.volume
        points = [QtCore.QPointF(x_, y_) for (x_, y_) in zip(x, y)]
        points.append(QtCore.QPointF(x[-1], 0))
        points.append(QtCore.QPointF(x[0], 0))
        p = QtGui.QPolygonF(points)
        
        painter.setPen(self.pen)
        painter.setBrush(self.brush)
        painter.drawPolygon(p)
    
    def boundingRect(self):
        """Returns the bounding rectangle of the data.
        
        For the y boundaries, uses low/high if they are used, or open/close
        otherwise.
        """
        if not self.HasData():
            return Qt.QRectF(0, 0, 0, 0)
        td = self._data
        x = td.timestamp2
        xmin = np.min(x)
        xmax = np.max(x)
        ymax = np.max(td.volume)
        try:
            return Qt.QRectF(xmin, 0, xmax-xmin, ymax)
        except:
            pass
 
